using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Newtonsoft.Json;
using Sparklings.Gui;
using Sparklings.Save;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Sparklings
{
    public enum GameState
    {
        LevelChoice,
        LevelInfo,
        LevelEnd,
        InLevel,
        FatalErrorInfo,
    }
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class SparklingsGame : Microsoft.Xna.Framework.Game, ILevelNeededData
    {
        public GraphicsDeviceManager Graphics;
        SpriteBatch SpriteBatch;
        Color backgroundColor;
        int screenSize = 500;
        public Random RNG { get; set; }
        public Level CurrentLevel;
        LevelContainer LevelContainer;
        public LevelMetaData CurrentLevelMetaData;
        GameState GameState;
        SpriteFont MenuFont;
        bool wasMouseButtonDown;
        string InfoText;
        SavedData PlayerProgress = new SavedData();

        LevelChoiceMap LevelChoiceMap;
        public SparklingsGame()
        {
            Graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            backgroundColor = Color.White;
            RNG = new Random();
            GameState = GameState.LevelChoice;
            Window.Title = "Sparklings";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            this.IsMouseVisible = true;
            Graphics.PreferredBackBufferWidth = screenSize;
            Graphics.PreferredBackBufferHeight = screenSize;
            Graphics.IsFullScreen = false;
            Graphics.ApplyChanges();
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            MenuFont = Content.Load<SpriteFont>("DefaultFont");
            SpriteBatch = new SpriteBatch(Graphics.GraphicsDevice);
            string folder = Content.RootDirectory + "/Levels/";
            string[] files = Directory.GetFiles(folder, "*.lvl");
            var levels = new List<ChoiceListElement>();
            foreach (var fileName in files)
            {
                try
                {
                    levels.Add(new LevelMetaData(fileName));
                }
                catch { } // Ignoring the entry.
            }
            //level = Level.Load(this, File.ReadAllText(files[0]));
            //LevelChoiceMap = new ChoiceList(MenuFont, levels, 30, new Rectangle(0, 0, 500, 500));
            LoadProgress();
            try
            {
                LevelChoiceMap = new LevelChoiceMap(
                    Graphics.GraphicsDevice,
                    MenuFont,
                    new Rectangle(0, 0, 500, 500),
                    Content.RootDirectory + "/Levels/w1/info.wdata",
                    PlayerProgress);
                LevelChoiceMap.OnSelect = StartLevel;
                LevelChoiceMap.OnSelectNotExisting = (s) => SetFatalError("Failed to load level \"" + s + "\". Please reinstall the game.");
            }
            catch
            {
                SetFatalError("Failed to load the world, please reinstall the game.");
            }
            if (CurrentLevel != null)
            {
                CurrentLevel.GraphicsDevice = Graphics.GraphicsDevice;
                CurrentLevel.LoadGraphicsRelatedElements();
            }
            if (LevelContainer != null)
            {
                LevelContainer.LoadGraphicsRelatedContent(Graphics.GraphicsDevice);
            }
        }
        public void StartLevel(LevelMetaData item)
        {
            CurrentLevel = null;
            try
            {
                CurrentLevel = Level.Load(this, File.ReadAllText((item as LevelMetaData).FilePath));
                CurrentLevelMetaData = item as LevelMetaData;
                if (CurrentLevel.HelpText != "")
                {
                    InfoText = CurrentLevel.HelpText;
                    GameState = GameState.LevelInfo;
                }
                else
                {
                    GameState = GameState.InLevel;
                }
                LevelContainer = new LevelContainer(this, CurrentLevel, new Rectangle(0, 0, 500, 500));
            }
            catch
            {
                SetFatalError("Level \"" + (item as LevelMetaData).FilePath + "\" is corrupted. Please reinstall the game.");
            }
        }
        public void ExitLevel()
        {
            GameState = GameState.LevelChoice;
        }
        private static readonly string SaveFileName = Path.Combine(Environment.GetEnvironmentVariable("appdata"), "Sparklings", "main.sav");
        protected void LoadProgress()
        {
            try
            {
                PlayerProgress = JsonConvert.DeserializeObject<SavedData>(File.ReadAllText(SaveFileName));
            }
            catch { }

            if (PlayerProgress == null)
            {
                // No saved data
                PlayerProgress = new SavedData();
            }
        }

        protected void SaveProgress()
        {
            Directory.CreateDirectory(Path.GetDirectoryName(SaveFileName));
            File.WriteAllText(SaveFileName, JsonConvert.SerializeObject(PlayerProgress));
        }

        protected void SetCurrentLevelAsFinished()
        {
            LevelProgressData bestResult = PlayerProgress.LevelsFinished.FirstOrDefault(e => e.Name == Path.GetFileName(CurrentLevelMetaData.FilePath));
            if (bestResult == null)
            {
                PlayerProgress.LevelsFinished.Add(new LevelProgressData(Path.GetFileName(CurrentLevelMetaData.FilePath)) { StarsTaken = CurrentLevel.StarsTaken, StarsLeft = CurrentLevel.StarsLeft });
            }
            else if (bestResult.StarsTaken < CurrentLevel.StarsTaken)
            {
                bestResult.StarsTaken = CurrentLevel.StarsTaken;
                bestResult.StarsLeft = CurrentLevel.StarsLeft;
            }
        }

        public void SetFatalError(string s)
        {
            InfoText = s;
            GameState = GameState.FatalErrorInfo;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            {
                this.Exit();
            }
            bool click = false;
            if (wasMouseButtonDown && Mouse.GetState().LeftButton == ButtonState.Released)
            {
                if (new Rectangle(0, 0, screenSize, screenSize).Contains(new Point(Mouse.GetState().X, Mouse.GetState().Y)))
                {
                    click = true;
                }
            }
            wasMouseButtonDown = Mouse.GetState().LeftButton == ButtonState.Pressed;
            switch (GameState)
            {
                case GameState.LevelChoice:
                    LevelChoiceMap.Update(gameTime);
                    break;
                case GameState.LevelInfo:
                    if (click)
                    {
                        GameState = CurrentLevel != null ? GameState.InLevel : GameState.LevelChoice;
                    }
                    break;
                case GameState.InLevel:
                    //CurrentLevel.Update(gameTime);
                    LevelContainer.Update(gameTime,click);
                    if (CurrentLevel.SparklingsNeeded == 0)
                    {
                        SetCurrentLevelAsFinished();
                        SaveProgress();
                        GameState = GameState.LevelEnd;
                    }
                    break;
                case GameState.LevelEnd:
                    //CurrentLevel.Update(gameTime);
                    LevelContainer.Update(gameTime,click);
                    if (click)
                    {
                        GameState = GameState.LevelChoice;
                    }
                    break;
                case GameState.FatalErrorInfo:
                    break;
            }
            base.Update(gameTime);
        }
        string SplitStringToFit(string s, int maxChars)
        {
            string[] lines = s.Split('\n');
            StringBuilder newSentence = new StringBuilder();
            foreach (string line_ in lines)
            {
                // Copy pasted from http://stackoverflow.com/questions/10541124
                string[] words = line_.Split(' ');
                string line = "";
                foreach (string word in words)
                {
                    if ((line + word).Length > maxChars)
                    {
                        newSentence.AppendLine(line);
                        line = "";
                    }
                    line += string.Format("{0} ", word);
                }
                if (line.Length > 0)
                {
                    newSentence.AppendLine(line);
                }
            }
            return newSentence.ToString();
        }
        protected Texture2D DrawFullScreenInfo(string s)
        {
            int maxCharPerLine = (int)(screenSize / 11);
            string lineLengthCappedString = SplitStringToFit(s,maxCharPerLine);
            var renderTarget = new RenderTarget2D(Graphics.GraphicsDevice, screenSize, screenSize);
            Graphics.GraphicsDevice.SetRenderTarget(renderTarget);
            Graphics.GraphicsDevice.Clear(Color.DarkGreen);
            SpriteBatch.Begin();
            {
                SpriteBatch.DrawString(MenuFont, lineLengthCappedString, new Vector2(), Color.White);
            }
            SpriteBatch.End();
            Graphics.GraphicsDevice.SetRenderTarget(null);
            return renderTarget;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            //Graphics.GraphicsDevice.Disposing += (a, b) => { throw new Exception("wtf"); };

            Texture2D levelTex = null;
            switch (GameState)
            {
                case GameState.LevelChoice:
                    levelTex = LevelChoiceMap.GetDrawn(Graphics.GraphicsDevice);
                    break;
                case GameState.FatalErrorInfo:
                case GameState.LevelInfo:
                    levelTex = DrawFullScreenInfo(InfoText);
                    break;
                case GameState.InLevel:
                    levelTex = LevelContainer.GetDrawn(Graphics.GraphicsDevice); //CurrentLevel.GetDrawn(Graphics.GraphicsDevice, screenSize);
                    break;
                case GameState.LevelEnd:
                    levelTex = LevelContainer.GetDrawn(Graphics.GraphicsDevice); //CurrentLevel.GetDrawn(Graphics.GraphicsDevice, screenSize);
                    break;
            }
            SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend/*, null, null, null, effect*/);
            SpriteBatch.Draw(levelTex, Vector2.Zero, Color.White);
            SpriteBatch.End();
            base.Draw(gameTime);
        }
    }
}
